-
Notifications
You must be signed in to change notification settings - Fork 1
[FEAT] 하루한컷 업로드 #14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[FEAT] 하루한컷 업로드 #14
Conversation
Walkthroughpackage.json에서 의존성 하나가 제거되고, 다수의 신규 페이지 · UI 컴포넌트 · 훅 · 유틸 · 타입 · 목데이터가 추가되며 일부 공통 컴포넌트의 API가 확장되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant DayMissionCard as DailyMissionCard (UI)
participant Hook as useImageUpload
participant Util as uploadImage
participant Router as NextRouter
User->>DayMissionCard: 사진 선택 요청 (파일 선택 버튼 클릭)
DayMissionCard->>Hook: openFilePicker()
Hook->>User: 브라우저 파일 선택창 표시
User->>Hook: 파일 선택
Hook->>Util: uploadImage(file)
Util-->>Hook: { file, previewUrl } 또는 오류
Hook-->>DayMissionCard: onSelect(result)
DayMissionCard->>DayMissionCard: previewUrl 설정, 상태 변경 -> IMAGE_UPLOADED
User->>DayMissionCard: 확인 버튼 클릭
DayMissionCard->>Router: router.push('/day-log') (확인 시)
sequenceDiagram
actor User
participant SharedDiary as SharedDiaryComment (컴포넌트)
participant MessageInput as MessageInput
participant CommentList as CommentList
participant Router as NextRouter
User->>MessageInput: 입력 필드 포커스
MessageInput->>SharedDiary: onFocus
alt modal 모드인 경우
SharedDiary->>Router: router.push('/shared-diary/{id}/comment')
end
User->>MessageInput: 메시지 입력 후 엔터 또는 전송 클릭
MessageInput->>SharedDiary: onSend(message)
SharedDiary->>CommentList: comments 상태에 새 댓글 추가
SharedDiary-->>User: 업데이트된 댓글 목록 렌더링
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60분 Possibly related PRs
시
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
🤖 Fix all issues with AI Agents
In @package.json:
- Line 20: The package.json entry for "@use-funnel/next" references a
non-existent v0.0.22 and may be incompatible with our React/Next versions;
change the dependency to the published v0.0.21 (or verify v0.0.22 exists) and
then audit the codebase for the library's breaking changes: replace array step
definitions with createFunnelSteps(), swap any <Funnel> / <Funnel.Step> usage to
funnel.Render or conditional rendering, migrate state HOC usage of withState()
to createFunnelSteps generic types, and replace onStepChange callbacks with
useEffect reading funnel.step; also confirm our React/Next versions match the
library's supported versions before committing the dependency change.
In @src/app/day-log/page.tsx:
- Line 10: The hardcoded MY_ID = 111 in src/app/day-log/page.tsx (also
duplicated in src/components/dailyRecord/MissionFeed.tsx) is dev-only mock data;
refactor by removing the duplicate constant and either (a) extract a shared
constant (e.g., export const DEV_MOCK_USER_ID) in a single module and import it
where needed for local testing, or (b) prefer passing userId as a prop from the
parent (page.tsx) into MissionFeed, and replace MY_ID usage with that prop;
additionally prepare for production by replacing the mock with a call to your
auth/context provider (e.g., getUserIdFromAuth or useAuth().user.id) so runtime
derives the real user id when authentication is available.
In @src/app/shared-diary/upload/page.tsx:
- Line 1: The file src/app/shared-diary/upload/page.tsx is an empty placeholder
and will render a blank page; either implement the page component (export
default async function SharedDiaryUploadPage() { ... }) with the form and
handlers, or replace the file contents with a simple placeholder UI (e.g., a
"Coming Soon / Under Construction" React component) or disable the route by
removing its export; pick one approach, add basic accessibility and skeleton
markup, and open an issue referencing "shared-diary upload page implementation"
if you want to defer full work.
In @src/components/common/BackHeader.tsx:
- Line 30: BackHeader uses a dynamic Tailwind class `text-${titleColor}` which
JIT won't generate; replace the dynamic string with a deterministic class
mapping or conditional rendering: create a small map or switch keyed by the
titleColor prop (e.g., map titleColor values to 'text-white', 'text-black',
etc.) and apply the resulting class to BackIcon and the other affected element
instead of `text-${titleColor}`, or alternatively render conditional classNames
for known values to ensure Tailwind generates the CSS.
In @src/components/dailyPhoto/DailyMissionCard.tsx:
- Line 35: In DailyMissionCard update the conditional className expression to
use strict equality (replace `==` with `===`) when comparing the `status`
variable so the ternary `status === "NOT_STARTED" ? "bg-gradient-orange" :
"bg-green-01"` is used, ensuring type-safe comparison in the JSX expression that
builds the className string.
In @src/components/dailyRecord/SharedDiaryChat.tsx:
- Around line 26-27: Validate the route id from useParams before using
Number(id): ensure id exists and parses to a finite integer (e.g.,
parseInt/Number + Number.isFinite/Number.isInteger and > 0) and only assign
numericId when valid; if invalid, short-circuit the component (return null or an
error UI) or perform a safe redirect (using your routing navigate) instead of
proceeding to the routing logic that expects a number — update the numericId
assignment and add the guard around any code paths that use numericId
(references: useParams, id, numericId).
In @src/utils/formatDate.ts:
- Around line 1-8: formatDate currently creates a Date from createdAt without
validation, producing "aN년 aN월aN일" for invalid inputs; update the formatDate
function to validate the Date (e.g., const date = new Date(createdAt); if
(isNaN(date.getTime())) { ... }) and handle invalid cases by either throwing a
clear error, returning a safe fallback string (like "Invalid date" or ""), or
accepting an optional default value; ensure all downstream callers can handle
the chosen fallback and keep the returned type string.
🧹 Nitpick comments (13)
src/mock/diaryComment.json (1)
1-23: 목 데이터의 댓글 내용 다양화 필요.이미지 파일(
/images/1.jpg,/images/2.jpg,/images/3.jpg)은 실제로public/images/디렉토리에 존재합니다. 그러나 댓글 내용이 모두 "코코넛 커피도 마셔주세요"로 동일하며 숫자만 추가된 형태입니다. 더 현실적인 테스트를 위해 댓글 내용을 다양하게 작성하는 것을 권장합니다.src/assets/SendButton.tsx (1)
9-16: 접근성 개선 제안: disabled 상태 추가현재
hasText가 false일 때cursor-pointer만 제거하고 버튼은 여전히 클릭 가능합니다.MessageInput.tsx에서 가드 처리하고 있지만, 더 나은 접근성과 의미론을 위해hasText가 false일 때 버튼에disabled속성을 추가하는 것을 고려해보세요.🔎 제안하는 개선 방안
export const SendButton = ({ hasText = false, onClick }: SendButtonProps) => { return ( <button className="absolute top-1 right-4 rotate-[-23deg] px-[2px] py-[3px]" onClick={onClick} + disabled={!hasText} > <SendIcon - className={`text-neutral-04 h-[18px] w-[20px] ${hasText ? "cursor-pointer" : ""}`} + className={`h-[18px] w-[20px] ${hasText ? "text-neutral-04 cursor-pointer" : "text-neutral-07"}`} /> </button> ); };src/components/dailyRecord/MissionFeed.tsx (2)
42-47: 접근성 개선 필요: 클릭 가능한 div에 키보드 접근성이 없습니다.현재
div에onClick만 있어 키보드 사용자가 접근할 수 없습니다. 접근성을 위해role,tabIndex,onKeyDown추가를 권장합니다.🔎 접근성 개선 제안
- <div - className="bg-neutral-11 flex h-[88px] w-[88px] items-center justify-center rounded-full" - onClick={() => router.push("/day-story/upload")} - > + <div + role="button" + tabIndex={0} + className="bg-neutral-11 flex h-[88px] w-[88px] items-center justify-center rounded-full" + onClick={() => router.push("/day-story/upload")} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + router.push("/day-story/upload"); + } + }} + >
11-11: 하드코딩된 MY_ID 관련 참고사항.
MY_ID가 컴포넌트 내부에 하드코딩되어 있습니다. TODO 주석(Line 16)에서 언급된 대로 API 연결 시 인증 상태 관리(zustand 등)에서 가져오도록 개선이 필요합니다.src/app/day-story/upload/page.tsx (1)
8-8: 레이아웃 안정성을 위해 min-height 추가 고려.
bg-black배경이 뷰포트 전체를 채우도록min-h-screen을 추가하면 콘텐츠가 적을 때도 일관된 레이아웃을 유지할 수 있습니다.🔎 제안된 수정
- <main className="w-full bg-black"> + <main className="min-h-screen w-full bg-black">src/app/shared-diary/[id]/comment/page.tsx (1)
1-1: 파일명과 컴포넌트명 불일치 참고.
SharedDiaryChat.tsx파일에서SharedDiaryComment를 export하고 있어 명명 규칙에 일관성이 없습니다. 유지보수성을 위해 파일명을SharedDiaryComment.tsx로 변경하거나, export 이름을 파일명과 맞추는 것을 고려해 보세요.src/components/dailyRecord/MissionInfo.tsx (1)
29-31: 접근성 개선 필요: 클릭 가능한 div에 키보드 접근성이 없습니다.MissionFeed.tsx와 동일하게, 클릭 가능한 영역에 키보드 접근성을 추가하는 것이 좋습니다.
🔎 접근성 개선 제안
<div - className="flex h-[76px] cursor-pointer items-center justify-between p-[10px]" + role="button" + tabIndex={0} + className="flex h-[76px] cursor-pointer items-center justify-between p-[10px]" onClick={() => router.push("/day-story/upload")} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + router.push("/day-story/upload"); + } + }} >src/constants/missionCard.ts (1)
4-4: subtitle 타입 일관성 개선 고려.
NOT_STARTED.subtitle은 함수이고 다른 상태는 문자열이라 소비 측에서typeof체크가 필요합니다. 모두 함수로 통일하거나, 별도 필드로 분리하면 타입 안정성이 향상됩니다.🔎 타입 일관성 개선 예시
// 옵션 1: 모든 subtitle을 함수로 통일 export const MISSION_STATUS = { NOT_STARTED: { title: "하루한컷 미션 도착!", subtitle: (localday: string) => `${localday}요일`, buttonText: "시작하기", }, IMAGE_UPLOADED: { title: "하루한컷 미션을 공유할까요?", subtitle: () => "확인을 누르면 미션이 완료돼요", buttonText: "완료하기", }, IMAGE_CONFIRMED: { title: "미션이 완료되었어요", subtitle: () => "소중한 오늘의 한컷을 기록했어요", buttonText: "확인", }, } as const;src/components/dailyPhoto/DailyMissionCard.tsx (2)
10-11: 사용되지 않는 상태 setter를 확인하세요.
setStatus가 선언되었지만 컴포넌트 내에서 사용되지 않고 있습니다. 향후 미션 상태 변경 기능을 구현할 예정이라면 TODO 코멘트를 추가하거나, 현재 사용하지 않는다면 상태 관리를 제거하고const status = "NOT_STARTED"로 단순화하는 것을 고려해보세요.
34-38: 버튼에 이벤트 핸들러 추가를 고려하세요.버튼이
cursor-pointer스타일을 가지고 있지만onClick핸들러가 없습니다. 향후 작업에서 버튼 클릭 시 상태 변경 또는 페이지 이동 등의 기능을 추가할 예정이라면, TODO 주석을 남겨두는 것이 좋습니다.src/components/dailyPhoto/DailyMissionProgress.tsx (1)
15-15: 하드코딩된 값을 동적 데이터로 교체할 계획을 확인하세요.
isTodayCompleted가 현재false로 하드코딩되어 있습니다. 향후 실제 미션 완료 상태를 반영하도록 props로 전달받거나 API에서 가져오는 방식으로 개선이 필요합니다. PR 설명의 "추후 작업" 섹션에 이 내용이 포함되어 있는지 확인해주세요.src/components/dailyRecord/SharedDiaryChat.tsx (2)
5-6: React 훅 임포트를 하나로 통합할 수 있습니다.가독성을 위해
useState와useEffect를 한 줄로 임포트하는 것을 권장합니다.🔎 제안하는 수정
-import { useState } from "react"; -import { useEffect } from "react"; +import { useState, useEffect } from "react";
85-96: 새 댓글 ID 생성 방식을 개선하는 것을 고려하세요.
Date.now()를 ID로 사용하는 것은 임시 솔루션으로는 적절하지만, 실제 API 연동 시 서버에서 생성된 ID를 사용하도록 리팩토링이 필요합니다. 또한MyProfile데이터의 존재 여부를 검증하는 것도 고려해보세요.💡 참고사항
현재는 로컬 상태에만 댓글이 추가되므로, 추후 API 연동 시 다음을 고려하세요:
- 서버에서 반환된 ID 사용
- 낙관적 업데이트(optimistic update) 패턴 적용
- 에러 처리 및 롤백 로직
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (3)
src/assets/left-arrow.svgis excluded by!**/*.svgsrc/assets/questionMark.svgis excluded by!**/*.svgyarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (24)
package.jsonsrc/app/day-log/page.tsxsrc/app/day-story/upload/page.tsxsrc/app/shared-diary/[id]/comment/page.tsxsrc/app/shared-diary/upload/page.tsxsrc/assets/SendButton.tsxsrc/components/common/BackHeader.tsxsrc/components/common/MessageInput.tsxsrc/components/dailyPhoto/DailyMissionCard.tsxsrc/components/dailyPhoto/DailyMissionProgress.tsxsrc/components/dailyRecord/CommentList.tsxsrc/components/dailyRecord/Header.tsxsrc/components/dailyRecord/MissionFeed.tsxsrc/components/dailyRecord/MissionInfo.tsxsrc/components/dailyRecord/ShareDiaryItem.tsxsrc/components/dailyRecord/SharedDiaryChat.tsxsrc/constants/dayToKorean.tssrc/constants/missionCard.tssrc/constants/navBarItems.tssrc/mock/diaryComment.jsonsrc/styles/globals.csssrc/types/diaryComment.type.tssrc/utils/formatDate.tssrc/utils/getCurrentDay.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-01-02T13:08:01.441Z
Learnt from: lemoncurdyogurt
Repo: IT-Cotato/12th-SimTok-FE PR: 3
File: src/utils/formatPhone.ts:6-9
Timestamp: 2026-01-02T13:08:01.441Z
Learning: In phone number formatting/validation logic, enforce that Korean numbers are valid only as 10-digit landlines or 11-digit mobile numbers starting with 010. Do not perform strict intermediate-length formatting for 7–9 digits during input; assume intermediate values are not considered valid until they reach 10 or 11 digits. Implement or update validation to accept only 10 or 11 digits (with 010 prefix for mobiles) and, if formatting is needed during input, preserve user-friendly partial handling without emitting strict validation errors for intermediate states.
Applied to files:
src/utils/formatDate.tssrc/utils/getCurrentDay.ts
🧬 Code graph analysis (7)
src/components/dailyPhoto/DailyMissionCard.tsx (3)
src/constants/missionCard.ts (1)
MISSION_STATUS(1-17)src/utils/getCurrentDay.ts (1)
getTodayIndex(5-8)src/constants/dayToKorean.ts (1)
WEEK_DAYS(1-1)
src/app/shared-diary/[id]/comment/page.tsx (1)
src/components/dailyRecord/SharedDiaryChat.tsx (1)
SharedDiaryComment(21-102)
src/app/day-story/upload/page.tsx (3)
src/components/common/BackHeader.tsx (1)
BackHeader(15-49)src/components/dailyPhoto/DailyMissionProgress.tsx (1)
DailyMissionProgress(10-49)src/components/dailyPhoto/DailyMissionCard.tsx (1)
DailyMissionCard(9-41)
src/components/dailyRecord/SharedDiaryChat.tsx (3)
src/types/myProfile.type.ts (1)
MyProfile(1-6)src/components/dailyRecord/CommentList.tsx (1)
CommentList(11-45)src/components/common/MessageInput.tsx (1)
MessageInput(10-41)
src/components/dailyRecord/ShareDiaryItem.tsx (1)
src/components/dailyRecord/SharedDiaryChat.tsx (1)
SharedDiaryComment(21-102)
src/components/dailyRecord/CommentList.tsx (2)
src/types/diaryComment.type.ts (1)
DiaryComment(1-7)src/utils/formatDate.ts (1)
formatDate(1-8)
src/components/common/MessageInput.tsx (1)
src/assets/SendButton.tsx (1)
SendButton(7-18)
🔇 Additional comments (27)
src/components/dailyRecord/Header.tsx (1)
16-16: LGTM!명시적인 Tailwind 스타일링이 추가되어 헤더 텍스트의 일관성이 개선되었습니다.
src/constants/navBarItems.ts (1)
25-25: 라우트 변경 검증 완료 - 문제 없음.새로운
/day-log라우트가src/app/day-log/page.tsx에 정상적으로 존재하며, 기존/record경로에 대한 참조는 코드베이스에서 완전히 제거되었습니다. navBarItems.ts의 "record" 키는 단순 식별자이며 실제 경로(href)는/day-log로 올바르게 설정되어 있습니다.src/styles/globals.css (3)
44-44: 색상 토큰 값 정리 확인됨16진수 색상 코드의 대소문자는 동일하게 작동하므로 이 변경은 코드 일관성을 위한 정리입니다.
48-90: AI 요약과 실제 코드의 불일치AI 요약에서는
scrollbar-hide와bg-radial-yellowgreen-mintgreen클래스가 제거되었다고 하지만, 제공된 코드(48-67번째 줄)에는 여전히 존재합니다. 나머지 gradient 클래스들의 포맷팅 변경은 일관성을 위한 것으로 보입니다.
91-93: 새로운 gradient 유틸리티 추가 확인됨하루한컷 업로드 기능을 위한 오렌지 그라데이션 유틸리티가 추가되었습니다. 구현이 명확하고 적절합니다.
src/components/common/MessageInput.tsx (4)
5-8: Props 인터페이스 정의 확인됨
MessageInputProps인터페이스가 명확하게 정의되었습니다. 옵셔널 props로 설계되어 유연한 사용이 가능합니다.
12-12: IME 조합 처리 구현 확인됨한글과 같은 조합형 문자 입력을 위한
isComposing상태 관리가 잘 구현되었습니다.onCompositionStart,onCompositionEnd,onKeyDown이벤트를 적절히 활용하여 조합 중에는 Enter 키로 전송되지 않도록 방지합니다. 이는 국제화된 입력 처리의 모범 사례입니다.Also applies to: 29-36
15-19: 메시지 전송 로직 확인됨
sendMessage함수가 적절하게 구현되었습니다. 빈 텍스트 검증, 공백 제거(trim), 전송 후 입력 필드 초기화가 모두 올바르게 처리됩니다.
24-24: 입력 필드 UX 개선 확인됨포커스 시 민트색 테두리를 표시하여 시각적 피드백을 제공하고,
onFocus이벤트를 상위 컴포넌트로 전달하여 유연한 동작(예: 전체 페이지로 이동)을 지원합니다.SendButton과의 연동도 적절히 구현되었습니다.Also applies to: 28-28, 38-38
src/assets/SendButton.tsx (1)
5-5: onClick prop 추가 확인됨
SendButton에onClick핸들러가 추가되어 클릭 동작을 외부에서 제어할 수 있게 되었습니다. 타입 정의가 올바르고 구현이 적절합니다.Also applies to: 7-7, 11-11
src/components/dailyRecord/ShareDiaryItem.tsx (1)
16-16: 컴포넌트 이름 변경 확인됨
SharedDiaryChat이SharedDiaryComment로 이름이 변경되었습니다. 이는 컴포넌트의 실제 기능(댓글 표시)을 더 정확하게 반영하는 의미론적 개선입니다.Also applies to: 92-92
src/app/day-story/upload/page.tsx (1)
6-16: LGTM! 페이지 구조가 잘 구성되어 있습니다.Server Component로 적절히 구현되었고, 클라이언트 컴포넌트들을 올바르게 합성하여 사용하고 있습니다.
src/types/diaryComment.type.ts (1)
1-7: LGTM! 타입 정의가 명확합니다.DiaryComment 타입이 필요한 필드를 잘 정의하고 있으며, mock 데이터 및 CommentList 컴포넌트와 일관성 있게 사용됩니다.
src/app/shared-diary/[id]/comment/page.tsx (1)
3-5: LGTM! 페이지 컴포넌트 구조가 적절합니다.variant prop을 활용하여 modal/page 분기를 잘 처리하고 있습니다.
src/components/dailyRecord/MissionInfo.tsx (1)
12-40: 조건부 렌더링 구조가 명확합니다.
hasMyRecord상태에 따른 UI 분기가 잘 구현되어 있고, 미완료 상태에서 업로드 페이지로의 네비게이션이 적절합니다.src/constants/missionCard.ts (1)
1-17: LGTM! 미션 상태 상수가 잘 정의되어 있습니다.
as const를 사용하여 타입 안정성을 확보했습니다.src/constants/dayToKorean.ts (1)
1-1: WEEK_DAYS 상수 정의가 적절합니다!
as const를 사용하여 리터럴 타입 추론이 올바르게 적용되었습니다. 배열이 월요일(index 0)부터 시작하는 것에 대해 우려할 필요는 없습니다.getCurrentDay.ts의getMondayStartIndex함수가 JavaScript의getDay()(일요일 = 0) 결과를 WEEK_DAYS 배열의 인덱싱(월요일 = 0)으로 정확하게 변환하고 있습니다:(day === 0 ? 6 : day - 1)이 변환으로 Sunday(0) → 6, Monday(1) → 0이 되어 완벽하게 매핑됩니다.
src/components/dailyPhoto/DailyMissionCard.tsx (1)
15-18: LGTM!subtitle 계산 로직이 함수형과 문자열형 subtitle을 모두 올바르게 처리하고 있습니다. 함수인 경우 "요일" 접미사를 추가하여 완전한 요일 표현을 생성하는 것이 적절합니다.
src/components/dailyPhoto/DailyMissionProgress.tsx (1)
17-46: LGTM!체크 아이콘 표시 로직과 상태별 스타일링이 올바르게 구현되었습니다. 과거 요일과 완료된 오늘 요일에 체크 표시를, 미래 요일과 미완료된 오늘 요일에 물음표를 표시하는 로직이 명확하고 정확합니다.
src/utils/getCurrentDay.ts (1)
1-18: LGTM!요일 인덱스 변환 및 상태 판단 로직이 정확하게 구현되었습니다.
getMondayStartIndex는 JavaScript의 일요일 시작(0) 인덱싱을 월요일 시작(0) 인덱싱으로 올바르게 변환하고,getWeekDayStatus는 대상 인덱스와 오늘 인덱스를 비교하여 과거/현재/미래를 정확하게 판단합니다.src/components/dailyRecord/CommentList.tsx (3)
1-9: LGTM!임포트 구조와 타입 정의가 명확하고 적절합니다.
11-19: LGTM!빈 상태 처리가 사용자 친화적이고 적절합니다.
25-31: 외부 이미지 URL은 현재 코드에서 사용되지 않습니다. 실제 데이터는/images/1.jpg와 같은 상대 경로를 사용하므로remotePatterns설정이 필요하지 않습니다. Next.js Image 컴포넌트는 public 디렉토리의 상대 경로를 기본적으로 지원합니다.Likely an incorrect or invalid review comment.
src/components/dailyRecord/SharedDiaryChat.tsx (4)
32-40: LGTM!모달 모드에서 배경 스크롤을 방지하는 로직이 올바르게 구현되어 있으며, 클린업 함수도 적절합니다.
43-52: LGTM!모달과 페이지 모드에 따른 조건부 렌더링과 이벤트 처리가 적절하게 구현되어 있습니다.
53-69: LGTM!섹션 레이아웃과
CommentList통합이 깔끔하게 구현되어 있습니다.
72-78: LGTM!이미지 크기 속성과 className이 일치하여 올바르게 설정되어 있습니다.
|
|
||
| const DailyRecordPage = () => { | ||
| const MY_ID = 101; | ||
| const MY_ID = 111; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if MY_ID is used elsewhere in the codebase
rg -n "MY_ID" --type=ts --type=tsxRepository: IT-Cotato/12th-SimTok-FE
Length of output: 95
🏁 Script executed:
# Examine the full context of the page.tsx file
cat -n src/app/day-log/page.tsxRepository: IT-Cotato/12th-SimTok-FE
Length of output: 1074
🏁 Script executed:
# Search for authentication-related patterns and user context
rg -n "useAuth|useSession|getCurrentUser|getUser|userId" --type=ts --type=tsx -A 2 | head -50Repository: IT-Cotato/12th-SimTok-FE
Length of output: 95
🏁 Script executed:
# Check if there's a git history showing the change from 101 to 111
git log -p --all -S "MY_ID" -- "src/app/day-log/page.tsx" 2>/dev/null | head -100Repository: IT-Cotato/12th-SimTok-FE
Length of output: 50
🏁 Script executed:
#!/bin/bash
# Search for MY_ID with general pattern matching
rg "MY_ID" -g "*.ts" -g "*.tsx" --type-list | head -20Repository: IT-Cotato/12th-SimTok-FE
Length of output: 457
🏁 Script executed:
# Try without file type restriction
rg -n "MY_ID" --include="*.ts" --include="*.tsx"Repository: IT-Cotato/12th-SimTok-FE
Length of output: 509
🏁 Script executed:
# Search for authentication/user context patterns
rg -n "(useAuth|useSession|getCurrentUser|getUser|userId|authContext)" --include="*.ts" --include="*.tsx" | head -40Repository: IT-Cotato/12th-SimTok-FE
Length of output: 509
🏁 Script executed:
# Check the mock data file to understand the structure
cat -n src/mock/dailyRecord.json | head -50Repository: IT-Cotato/12th-SimTok-FE
Length of output: 1352
🏁 Script executed:
# Search for other hardcoded IDs to understand the testing pattern
rg -n "= (10[0-9]|11[0-9])" --include="*.ts" --include="*.tsx" | grep -i "id\|user"Repository: IT-Cotato/12th-SimTok-FE
Length of output: 509
🏁 Script executed:
#!/bin/bash
# Simple search for MY_ID usage
rg "MY_ID" .Repository: IT-Cotato/12th-SimTok-FE
Length of output: 480
🏁 Script executed:
# Check if userId 111 exists in the full mock data file
cat src/mock/dailyRecord.json | grep -n "111"Repository: IT-Cotato/12th-SimTok-FE
Length of output: 50
🏁 Script executed:
# Check the full length of the mock data to see all userIds
tail -20 src/mock/dailyRecord.jsonRepository: IT-Cotato/12th-SimTok-FE
Length of output: 423
🏁 Script executed:
# Search for authentication/context patterns more simply
rg "useAuth\|useSession\|AuthContext\|UserContext" .Repository: IT-Cotato/12th-SimTok-FE
Length of output: 50
🏁 Script executed:
# Look at the git history of the dailyRecord file
git log --oneline src/mock/dailyRecord.json 2>/dev/null | head -10Repository: IT-Cotato/12th-SimTok-FE
Length of output: 92
하드코딩된 MY_ID는 개발용 모의 데이터입니다. 코드 중복 제거 및 인증 통합 준비 필요
MY_ID = 111은 모의 데이터와 함께 사용되는 개발 단계의 코드입니다. 실제로 dailyRecord.json의 모든 userId는 101~110 범위이므로, 111은 "레코드 없음" 시나리오를 의도적으로 테스트하는 것으로 확인됩니다.
다만, 다음 두 가지를 개선해야 합니다:
-
코드 중복: MY_ID = 111이
src/app/day-log/page.tsx와src/components/dailyRecord/MissionFeed.tsx에서 반복되고 있습니다. 공용 상수로 추출하거나 상위 컴포넌트에서 props로 전달하는 것을 권장합니다. -
프로덕션 준비: 현재 인증 시스템이 없으므로, 프로덕션 배포 전에 실제 인증 컨텍스트에서 사용자 ID를 가져오도록 리팩토링해야 합니다.
🤖 Prompt for AI Agents
In @src/app/day-log/page.tsx at line 10, The hardcoded MY_ID = 111 in
src/app/day-log/page.tsx (also duplicated in
src/components/dailyRecord/MissionFeed.tsx) is dev-only mock data; refactor by
removing the duplicate constant and either (a) extract a shared constant (e.g.,
export const DEV_MOCK_USER_ID) in a single module and import it where needed for
local testing, or (b) prefer passing userId as a prop from the parent (page.tsx)
into MissionFeed, and replace MY_ID usage with that prop; additionally prepare
for production by replacing the mock with a call to your auth/context provider
(e.g., getUserIdFromAuth or useAuth().user.id) so runtime derives the real user
id when authentication is available.
| className="absolute top-1/2 left-4 -translate-y-1/2 cursor-pointer" | ||
| > | ||
| <BackIcon className="h-6 w-6" /> | ||
| <BackIcon className={`text-${titleColor} h-6 w-6`} /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
동적 Tailwind 클래스명이 작동하지 않습니다.
Tailwind CSS의 JIT 컴파일러는 빌드 타임에 정적 분석을 통해 클래스를 생성합니다. text-${titleColor} 형태의 동적 클래스명은 인식되지 않아 스타일이 적용되지 않습니다.
🔎 해결 방법
방법 1 (권장): 조건부 렌더링 사용
- <BackIcon className={`text-${titleColor} h-6 w-6`} />
+ <BackIcon className={`${titleColor === "white" ? "text-white" : "text-black"} h-6 w-6`} /> <h1
- className={`text-h1 flex w-full items-center justify-center whitespace-nowrap text-${titleColor}`}
+ className={`text-h1 flex w-full items-center justify-center whitespace-nowrap ${titleColor === "white" ? "text-white" : "text-black"}`}
>방법 2: safelist에 추가 (tailwind.config에서)
// tailwind.config.js
module.exports = {
safelist: [
'text-white',
'text-black',
],
// ...
}방법 3: style prop 사용
- <BackIcon className={`text-${titleColor} h-6 w-6`} />
+ <BackIcon className="h-6 w-6" style={{ color: titleColor }} />방법 1이 타입 안전성과 Tailwind의 디자인 시스템 활용 측면에서 가장 권장됩니다.
Also applies to: 33-33
🤖 Prompt for AI Agents
In @src/components/common/BackHeader.tsx at line 30, BackHeader uses a dynamic
Tailwind class `text-${titleColor}` which JIT won't generate; replace the
dynamic string with a deterministic class mapping or conditional rendering:
create a small map or switch keyed by the titleColor prop (e.g., map titleColor
values to 'text-white', 'text-black', etc.) and apply the resulting class to
BackIcon and the other affected element instead of `text-${titleColor}`, or
alternatively render conditional classNames for known values to ensure Tailwind
generates the CSS.
| 오늘의 하늘을 공유해주세요. | ||
| </div> | ||
| <button | ||
| className={`${status == "NOT_STARTED" ? "bg-gradient-orange" : "bg-green-01"} text-button-sb h-[50px] w-[90px] cursor-pointer rounded-2xl text-white`} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
엄격한 동등 연산자를 사용하세요.
== 대신 ===를 사용하여 타입 안전성을 보장하세요.
🔎 수정 제안
- className={`${status == "NOT_STARTED" ? "bg-gradient-orange" : "bg-green-01"} text-button-sb h-[50px] w-[90px] cursor-pointer rounded-2xl text-white`}
+ className={`${status === "NOT_STARTED" ? "bg-gradient-orange" : "bg-green-01"} text-button-sb h-[50px] w-[90px] cursor-pointer rounded-2xl text-white`}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| className={`${status == "NOT_STARTED" ? "bg-gradient-orange" : "bg-green-01"} text-button-sb h-[50px] w-[90px] cursor-pointer rounded-2xl text-white`} | |
| className={`${status === "NOT_STARTED" ? "bg-gradient-orange" : "bg-green-01"} text-button-sb h-[50px] w-[90px] cursor-pointer rounded-2xl text-white`} |
🤖 Prompt for AI Agents
In @src/components/dailyPhoto/DailyMissionCard.tsx at line 35, In
DailyMissionCard update the conditional className expression to use strict
equality (replace `==` with `===`) when comparing the `status` variable so the
ternary `status === "NOT_STARTED" ? "bg-gradient-orange" : "bg-green-01"` is
used, ensuring type-safe comparison in the JSX expression that builds the
className string.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In @src/components/dailyRecord/SharedDiaryChat.tsx:
- Around line 26-27: The code converts useParams()'s id into numericId
unconditionally, which yields NaN when id is undefined and leads to invalid
navigation in SharedDiaryChat (see numericId and router.push). Fix by guarding
the conversion: check that id from useParams is defined and a valid number (use
Number/id !== undefined and !isNaN(Number(id)) or parseInt + isFinite), only set
numericId when valid, and before calling router.push ensure numericId is a valid
number (otherwise abort or use a safe fallback such as omitting numeric segment
or using the raw id string); update any places that assume numericId (e.g.,
navigation and API calls) to handle the undefined/invalid case safely.
🧹 Nitpick comments (2)
src/components/dailyRecord/SharedDiaryChat.tsx (2)
5-6: React import 통합 권장
useState와useEffect를 별도 줄에서 import하고 있습니다. 하나의 import 문으로 통합하면 코드가 더 간결해집니다.♻️ 제안된 수정
-import { useState } from "react"; -import { useEffect } from "react"; +import { useState, useEffect } from "react";
60-65: 접근성 개선: onClick을 button에 배치
onClick핸들러가CloseIconSVG에 있으나, 접근성을 위해<button>요소에 배치해야 합니다. 키보드 사용자와 스크린 리더가 버튼을 올바르게 인식하고 상호작용할 수 있습니다.♻️ 제안된 수정
- <button className="h-[14px] w-[14px] cursor-pointer"> - <CloseIcon - onClick={() => router.back()} - className="h-[14px] w-[14px] text-black" - /> + <button + className="h-[14px] w-[14px] cursor-pointer" + onClick={() => router.back()} + aria-label="닫기" + > + <CloseIcon className="h-[14px] w-[14px] text-black" /> </button>
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
src/assets/close.svgis excluded by!**/*.svg
📒 Files selected for processing (3)
src/components/dailyPhoto/DailyMissionProgress.tsxsrc/components/dailyRecord/SharedDiaryChat.tsxsrc/utils/getCurrentDay.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- src/utils/getCurrentDay.ts
- src/components/dailyPhoto/DailyMissionProgress.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
src/components/dailyRecord/SharedDiaryChat.tsx (2)
src/components/dailyRecord/CommentList.tsx (1)
CommentList(11-45)src/components/common/MessageInput.tsx (1)
MessageInput(10-41)
🔇 Additional comments (3)
src/components/dailyRecord/SharedDiaryChat.tsx (3)
32-40: LGTM!modal variant에서 body scroll을 비활성화하고 cleanup 시 복원하는 로직이 올바르게 구현되어 있습니다.
82-99: LGTM!
MessageInput의onFocus와onSend콜백 구현이 적절합니다. modal variant에서 focus 시 페이지로 전환하고, 댓글 전송 시 로컬 상태를 올바르게 업데이트합니다.
43-57: LGTM!modal/page variant에 따른 조건부 스타일링과 backdrop 클릭 처리가 적절하게 구현되어 있습니다.
stopPropagation을 사용하여 컨텐츠 클릭 시 의도치 않은 네비게이션을 방지하고 있습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In @src/components/dailyPhoto/DailyMissionCard.tsx:
- Around line 16-19: The DailyMissionCard component leaks Blob URLs because
previewUrl created with URL.createObjectURL is never revoked; add a useEffect in
DailyMissionCard that watches previewUrl and on cleanup (when previewUrl changes
or component unmounts) calls URL.revokeObjectURL(previewUrl) if previewUrl is
non-null, and ensure any code that sets a new previewUrl revokes the previous
one via the effect to avoid memory leaks.
- Around line 35-46: The callback is creating a duplicate Blob URL via
URL.createObjectURL in onSelectImage even though the uploadImage utility already
returns a previewUrl, causing potential memory leaks; modify the useImageUpload
onSelect handler (and onSelectImage) to accept and use the previewUrl provided
by uploadImage instead of calling URL.createObjectURL(file),
setPreviewUrl(previewUrl) and setStatus("IMAGE_UPLOADED") accordingly, and
remove the extra URL.createObjectURL usage so only the single previewUrl from
uploadImage is used (ensure any existing cleanup/revoke logic remains intact).
🧹 Nitpick comments (5)
src/utils/uploadImage.util.ts (1)
31-34:URL.createObjectURL로 생성된 URL의 메모리 해제가 필요합니다.
URL.createObjectURL로 생성된 Blob URL은URL.revokeObjectURL로 해제하지 않으면 메모리 누수가 발생할 수 있습니다. cleanup 함수를 반환하거나, 사용처에서 해제 책임이 있음을 문서화하는 것을 권장합니다.♻️ cleanup 함수를 포함한 개선안
export interface UploadImageResult { file: File; previewUrl: string; + revokePreview: () => void; } export const uploadImage = ( file: File, options: UploadImageOptions = {}, ): UploadImageResult => { // ... validation logic ... + const previewUrl = URL.createObjectURL(file); return { file, - previewUrl: URL.createObjectURL(file), + previewUrl, + revokePreview: () => URL.revokeObjectURL(previewUrl), }; };src/utils/getCurrentDay.ts (1)
10-10:DayStatus타입을 export하는 것을 고려해주세요.
getWeekDayStatus함수의 반환 타입인DayStatus가 내부 타입으로 선언되어 있어, 소비자 측에서 타입을 직접 사용하기 어렵습니다.♻️ 타입 export 제안
-type DayStatus = "past" | "today" | "future"; +export type DayStatus = "past" | "today" | "future";src/constants/missionCard.ts (1)
19-28:MISSION_SORT에도as const를 추가하는 것을 권장합니다.
MISSION_STATUS는as const로 선언되어 있지만,MISSION_SORT는 그렇지 않아 타입 추론에 일관성이 없습니다.♻️ as const 추가
export const MISSION_SORT = [ { sort: "food", icon: "/images/missionIcon/food.svg" }, { sort: "plant", icon: "/images/missionIcon/plant.svg", }, { sort: "color", icon: "/images/missionIcon/color.svg" }, { sort: "time", icon: "/images/missionIcon/time.svg" }, { sort: "tv", icon: "/images/missionIcon/tv.svg" }, -]; +] as const;src/components/dailyPhoto/DailyMissionCard.tsx (2)
85-85:==대신===를 사용하세요.느슨한 동등 비교(
==) 대신 엄격한 동등 비교(===)를 사용하는 것이 권장됩니다.♻️ 수정 제안
- className={`${status == "NOT_STARTED" ? "bg-gradient-orange" : "bg-mint-01"} ...`} + className={`${status === "NOT_STARTED" ? "bg-gradient-orange" : "bg-mint-01"} ...`}
68-68: 중복border클래스 제거
border border-[4px]에서border클래스가 중복됩니다.border-[4px]만으로 충분합니다.♻️ 수정 제안
- className={`h-27 w-27 overflow-hidden rounded-full ${status === "IMAGE_CONFIRMED" ? "border border-[4px] border-white shadow-[0_0_12px_-1px_#00C362]" : ""}`} + className={`h-27 w-27 overflow-hidden rounded-full ${status === "IMAGE_CONFIRMED" ? "border-[4px] border-white shadow-[0_0_12px_-1px_#00C362]" : ""}`}
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
src/components/dailyPhoto/DailyMissionCard.tsxsrc/constants/missionCard.tssrc/hooks/useImageUpload.tssrc/utils/getCurrentDay.tssrc/utils/uploadImage.util.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-01-02T13:08:01.441Z
Learnt from: lemoncurdyogurt
Repo: IT-Cotato/12th-SimTok-FE PR: 3
File: src/utils/formatPhone.ts:6-9
Timestamp: 2026-01-02T13:08:01.441Z
Learning: In phone number formatting/validation logic, enforce that Korean numbers are valid only as 10-digit landlines or 11-digit mobile numbers starting with 010. Do not perform strict intermediate-length formatting for 7–9 digits during input; assume intermediate values are not considered valid until they reach 10 or 11 digits. Implement or update validation to accept only 10 or 11 digits (with 010 prefix for mobiles) and, if formatting is needed during input, preserve user-friendly partial handling without emitting strict validation errors for intermediate states.
Applied to files:
src/utils/uploadImage.util.tssrc/utils/getCurrentDay.ts
🧬 Code graph analysis (2)
src/hooks/useImageUpload.ts (1)
src/utils/uploadImage.util.ts (2)
UploadImageResult(6-9)uploadImage(11-35)
src/components/dailyPhoto/DailyMissionCard.tsx (4)
src/constants/missionCard.ts (2)
MISSION_STATUS(1-17)MISSION_SORT(19-28)src/utils/getCurrentDay.ts (1)
getTodayIndex(5-8)src/constants/dayToKorean.ts (1)
WEEK_DAYS(1-1)src/hooks/useImageUpload.ts (1)
useImageUpload(10-42)
🔇 Additional comments (3)
src/hooks/useImageUpload.ts (1)
10-42: LGTM!훅 구조가 잘 설계되어 있습니다. 에러 처리, 파일 입력 리셋, 그리고 관심사 분리가 적절하게 구현되어 있습니다.
src/utils/getCurrentDay.ts (1)
1-18: LGTM!월요일 기준 요일 인덱스 변환 로직이 정확하게 구현되어 있습니다.
src/components/dailyPhoto/DailyMissionCard.tsx (1)
48-103: 전반적으로 잘 구현되었습니다.상태 기반 UI 렌더링과 미션 카드 플로우가 명확하게 구현되어 있습니다. 위에서 언급한 메모리 관련 이슈만 수정하면 좋겠습니다.
| export const DailyMissionCard = () => { | ||
| const [status, setStatus] = | ||
| useState<keyof typeof MISSION_STATUS>("NOT_STARTED"); | ||
| const [previewUrl, setPreviewUrl] = useState<string | null>(null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Blob URL 정리를 위한 cleanup effect 추가 필요
컴포넌트 언마운트 시 또는 previewUrl 변경 시 이전 Blob URL을 해제하지 않으면 메모리 누수가 발생합니다.
🐛 cleanup effect 추가
+import { useState, useEffect } from "react";
export const DailyMissionCard = () => {
const [status, setStatus] =
useState<keyof typeof MISSION_STATUS>("NOT_STARTED");
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
+ useEffect(() => {
+ return () => {
+ if (previewUrl) {
+ URL.revokeObjectURL(previewUrl);
+ }
+ };
+ }, [previewUrl]);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const DailyMissionCard = () => { | |
| const [status, setStatus] = | |
| useState<keyof typeof MISSION_STATUS>("NOT_STARTED"); | |
| const [previewUrl, setPreviewUrl] = useState<string | null>(null); | |
| import { useState, useEffect } from "react"; | |
| export const DailyMissionCard = () => { | |
| const [status, setStatus] = | |
| useState<keyof typeof MISSION_STATUS>("NOT_STARTED"); | |
| const [previewUrl, setPreviewUrl] = useState<string | null>(null); | |
| useEffect(() => { | |
| return () => { | |
| if (previewUrl) { | |
| URL.revokeObjectURL(previewUrl); | |
| } | |
| }; | |
| }, [previewUrl]); |
🤖 Prompt for AI Agents
In @src/components/dailyPhoto/DailyMissionCard.tsx around lines 16 - 19, The
DailyMissionCard component leaks Blob URLs because previewUrl created with
URL.createObjectURL is never revoked; add a useEffect in DailyMissionCard that
watches previewUrl and on cleanup (when previewUrl changes or component
unmounts) calls URL.revokeObjectURL(previewUrl) if previewUrl is non-null, and
ensure any code that sets a new previewUrl revokes the previous one via the
effect to avoid memory leaks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
src/app/day-story/upload/page.tsx (2)
10-22: 컴포넌트 구조가 잘 구성되어 있습니다.상태 관리와 컴포넌트 구성이 깔끔하게 되어 있습니다. 몇 가지 개선 사항을 제안드립니다:
main요소에min-h-screen을 추가하면 다양한 화면 크기에서 레이아웃이 더 안정적으로 유지됩니다.mt-[143px]와 같은 매직 넘버는 상수로 추출하거나 주석을 달아두면 유지보수성이 향상됩니다.♻️ 제안된 개선 사항
const DayStoryUpload = () => { const [status, setStatus] = useState<keyof typeof MISSION_STATUS>("NOT_STARTED"); return ( - <main className="w-full bg-black"> + <main className="min-h-screen w-full bg-black"> <BackHeader title="하루한컷" titleColor="white" /> <DailyMissionProgress status={status} /> - <div className="mt-[143px] flex items-center justify-center"> + {/* BackHeader(63px) + DailyMissionProgress(90px) 이후 중앙 정렬을 위한 여백 */} + <div className="mt-[143px] flex items-center justify-center"> <DailyMissionCard status={status} setStatus={setStatus} /> </div> </main> ); };
15-15: 조건부 클래스 맵핑으로 동적 Tailwind 클래스 패턴 개선
BackHeader컴포넌트에서text-${titleColor}형태의 동적 클래스를 사용하고 있습니다. Tailwind CSS는 빌드 시점에 정적 분석을 수행하므로 동적으로 생성된 클래스명은 일반적으로 문제가 될 수 있습니다.다만, 현재 코드베이스에서
text-white와text-black이 다른 컴포넌트들에서 정적으로 사용되고 있어 실제 스타일 적용에는 문제가 없습니다. 그러나 유지보수성과 명확성을 위해 다음과 같이 수정을 권장합니다:const titleColorClass = { white: "text-white", black: "text-black" }[titleColor] || "text-black"; <BackIcon className={`${titleColorClass} h-6 w-6`} />이렇게 하면 Tailwind가 명시적으로 클래스명을 인식하고, 향후 새로운 색상이 추가될 때도 보다 안전합니다.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
src/app/day-story/upload/page.tsxsrc/components/dailyPhoto/DailyMissionCard.tsxsrc/components/dailyPhoto/DailyMissionProgress.tsxsrc/constants/weekDays.tssrc/mock/dailyRecordProgress.json
🚧 Files skipped from review as they are similar to previous changes (2)
- src/components/dailyPhoto/DailyMissionProgress.tsx
- src/components/dailyPhoto/DailyMissionCard.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
src/app/day-story/upload/page.tsx (4)
src/constants/missionCard.ts (1)
MISSION_STATUS(1-17)src/components/common/BackHeader.tsx (1)
BackHeader(15-49)src/components/dailyPhoto/DailyMissionProgress.tsx (1)
DailyMissionProgress(20-83)src/components/dailyPhoto/DailyMissionCard.tsx (1)
DailyMissionCard(21-110)
🔇 Additional comments (2)
src/mock/dailyRecordProgress.json (1)
1-9: LGTM!Mock 데이터 구조가 올바르며, weekDays.ts의
WEEK_DAY_KEYS와 키가 일치합니다. 완료/미완료 상태를 표현하기 위한 boolean 값 사용이 적절합니다.src/constants/weekDays.ts (1)
1-19: LGTM!두 배열의 인덱스가 올바르게 정렬되어 있고,
as const를 사용하여 타입 안전성을 확보했습니다. 월요일 기반 인덱싱(index 0 = 월요일)이 한국 로케일에 적합합니다.
getCurrentDay.ts에서 월요일 기반 인덱싱을 위한 변환 로직이 이미getMondayStartIndex()함수로 적절히 구현되어 있습니다. JavaScript의Date.getDay()(일요일=0)을 인덱스 변환 공식(day === 0 ? 6 : day - 1)을 통해 월요일 기반(월요일=0)으로 변환하고 있으므로, 상수와 유틸리티 함수 간 정렬이 완벽합니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
src/constants/missionCard.ts (1)
19-28:as constassertion 추가로 타입 안정성 강화
MISSION_STATUS와 달리MISSION_SORT에는as constassertion이 없습니다. 타입 추론의 일관성을 위해 추가하는 것을 권장합니다. 아이콘 경로는 모두 정상적으로 존재합니다.♻️ 제안된 수정
export const MISSION_SORT = [ { sort: "food", icon: "/images/missionIcon/food.svg" }, { sort: "plant", icon: "/images/missionIcon/plant.svg", }, { sort: "color", icon: "/images/missionIcon/color.svg" }, { sort: "time", icon: "/images/missionIcon/time.svg" }, { sort: "tv", icon: "/images/missionIcon/tv.svg" }, -]; +] as const;src/app/day-story/upload/page.tsx (1)
14-15: 동적 Tailwind 클래스 구성 및 레이아웃 개선 권장
BackHeader에서titleColorprop을 통해 동적으로 클래스를 구성하고 있습니다 (text-${titleColor}). 비록text-white와text-black이 코드베이스의 다른 곳에서 정적으로 사용되어 번들에 포함될 가능성이 높지만, 동적 클래스 구성은 유지보수성과 가독성 측면에서 모범 사례가 아닙니다. 가능하면 props나 조건부 로직으로 직접 클래스명을 결정하는 것을 권장합니다.또한
main요소에min-h-screen을 추가하여 전체 뷰포트 높이를 확보하는 것이 레이아웃 안정성을 높일 수 있습니다.♻️ 레이아웃 개선 제안
- <main className="w-full bg-black"> + <main className="min-h-screen w-full bg-black">
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/app/day-story/upload/page.tsxsrc/components/dailyPhoto/DailyMissionCard.tsxsrc/constants/missionCard.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- src/components/dailyPhoto/DailyMissionCard.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
src/app/day-story/upload/page.tsx (1)
src/constants/missionCard.ts (1)
MISSION_STATUS(1-17)
🔇 Additional comments (2)
src/constants/missionCard.ts (1)
1-17: LGTM!미션 상태에 대한 상수 정의가 잘 구성되어 있습니다.
as constassertion으로 타입 안전성도 확보되었습니다.src/app/day-story/upload/page.tsx (1)
10-22: 컴포넌트 구조 및 상태 관리 LGTM!상태 관리를 위한
useState사용과MISSION_STATUS의 키 타입을 활용한 타입 안전성 확보가 잘 되어 있습니다. 컴포넌트 구성도 적절합니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/components/dailyRecord/SharedDiaryChat.tsx`:
- Around line 59-65: The click handler is placed on the CloseIcon SVG but should
be on the parent button; move the onClick={() => router.back()} from the
CloseIcon element to the surrounding <button> so the button element handles the
click (keep CloseIcon as a presentational child and preserve className and
sizing on both the button and CloseIcon).
♻️ Duplicate comments (3)
src/components/dailyPhoto/DailyMissionCard.tsx (2)
90-90: 엄격한 동등 연산자를 사용하세요.
==대신===를 사용하여 타입 안전성을 보장하세요. 이전 리뷰에서도 지적된 사항입니다.
26-26: Blob URL 메모리 누수 방지를 위한 cleanup effect가 필요합니다.컴포넌트 언마운트 시 또는
previewUrl변경 시 이전 Blob URL을 해제하지 않으면 메모리 누수가 발생합니다. 이전 리뷰에서도 지적된 사항입니다.🐛 cleanup effect 추가
+import { useState, useEffect } from "react"; -import { useState } from "react"; export const DailyMissionCard = ({ status, setStatus, }: DailyMissionCardProps) => { const [previewUrl, setPreviewUrl] = useState<string | null>(null); + useEffect(() => { + return () => { + if (previewUrl) { + URL.revokeObjectURL(previewUrl); + } + }; + }, [previewUrl]);src/components/dailyRecord/SharedDiaryChat.tsx (1)
26-27: NaN 검증이 불완전합니다.현재 코드는
id가undefined인 경우만 처리하고,"abc"와 같은 유효하지 않은 문자열은 처리하지 않습니다.Number("abc")는NaN을 반환하며,NaN !== null은true이므로 Line 84-85의 조건을 통과하여/shared-diary/NaN/comment로 잘못된 네비게이션이 발생할 수 있습니다.🐛 제안된 수정
const { id } = useParams<{ id: string }>(); - const numericId = id ? Number(id) : null; + const numericId = id && !Number.isNaN(Number(id)) ? Number(id) : null;
🧹 Nitpick comments (5)
src/utils/getMissionSubtitle.ts (1)
4-15:todayIndex에 대한 유효성 검사가 누락되었습니다.
todayIndex가 유효 범위(0-6)를 벗어나면WEEK_DAYS_KOR[todayIndex]가undefined를 반환하여"undefined요일"같은 잘못된 문자열이 생성될 수 있습니다.♻️ 제안된 수정
export const getMissionSubtitle = ( status: keyof typeof MISSION_STATUS, todayIndex: number, ) => { + if (todayIndex < 0 || todayIndex > 6 || Number.isNaN(todayIndex)) { + throw new Error(`Invalid todayIndex: ${todayIndex}`); + } const currentDay = WEEK_DAYS_KOR[todayIndex]; const rawSubtitle = MISSION_STATUS[status].subtitle; const text = typeof rawSubtitle === "function" ? `${rawSubtitle(currentDay)}요일` : rawSubtitle; return text; };src/components/dailyPhoto/DailyMissionCard.tsx (2)
8-8: 사용되지 않는 import입니다.
WEEK_DAYS_KOR는 이 파일에서 직접 사용되지 않습니다.getMissionSubtitle유틸리티가 내부적으로 처리하므로 제거해주세요.🧹 제안된 수정
-import { WEEK_DAYS_KOR } from "@/constants/weekDays";
71-71: 중복된 Tailwind 클래스입니다.
border-[4px]가 이미 border를 정의하므로border는 불필요합니다.🧹 제안된 수정
- className={`h-27 w-27 overflow-hidden rounded-full ${status === "IMAGE_CONFIRMED" ? "border border-[4px] border-white shadow-[0_0_12px_-1px_#00C362]" : ""}`} + className={`h-27 w-27 overflow-hidden rounded-full ${status === "IMAGE_CONFIRMED" ? "border-[4px] border-white shadow-[0_0_12px_-1px_#00C362]" : ""}`}src/components/dailyRecord/SharedDiaryChat.tsx (2)
5-6: React 훅 import를 하나로 통합하세요.
useState와useEffect를 별도 import 문으로 분리할 필요가 없습니다.♻️ 제안된 수정
-import { useState } from "react"; -import { useEffect } from "react"; +import { useState, useEffect } from "react";
70-72: 하단 고정 footer와 겹치는 padding 값을 확인하세요.
pb-[109px]이 하드코딩되어 있는데, 이 값이 Line 74의 footer 높이와 동기화되어 있는지 확인이 필요합니다. Footer 높이가 변경되면 컨텐츠가 가려지거나 불필요한 공백이 생길 수 있습니다.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (5)
package.jsonsrc/components/dailyPhoto/DailyMissionCard.tsxsrc/components/dailyRecord/SharedDiaryChat.tsxsrc/utils/formatDate.tssrc/utils/getMissionSubtitle.ts
💤 Files with no reviewable changes (1)
- package.json
🚧 Files skipped from review as they are similar to previous changes (1)
- src/utils/formatDate.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-01-02T13:08:01.441Z
Learnt from: lemoncurdyogurt
Repo: IT-Cotato/12th-SimTok-FE PR: 3
File: src/utils/formatPhone.ts:6-9
Timestamp: 2026-01-02T13:08:01.441Z
Learning: In phone number formatting/validation logic, enforce that Korean numbers are valid only as 10-digit landlines or 11-digit mobile numbers starting with 010. Do not perform strict intermediate-length formatting for 7–9 digits during input; assume intermediate values are not considered valid until they reach 10 or 11 digits. Implement or update validation to accept only 10 or 11 digits (with 010 prefix for mobiles) and, if formatting is needed during input, preserve user-friendly partial handling without emitting strict validation errors for intermediate states.
Applied to files:
src/utils/getMissionSubtitle.ts
🧬 Code graph analysis (2)
src/utils/getMissionSubtitle.ts (2)
src/constants/missionCard.ts (1)
MISSION_STATUS(1-17)src/constants/weekDays.ts (1)
WEEK_DAYS_KOR(1-9)
src/components/dailyRecord/SharedDiaryChat.tsx (3)
src/types/myProfile.type.ts (1)
MyProfile(1-6)src/components/dailyRecord/CommentList.tsx (1)
CommentList(11-45)src/components/common/MessageInput.tsx (1)
MessageInput(10-41)
🔇 Additional comments (3)
src/components/dailyPhoto/DailyMissionCard.tsx (1)
43-48: LGTM!이미지 업로드 훅 통합이 올바르게 구현되었습니다. 이전 리뷰에서 지적된 중복
URL.createObjectURL문제가 해결된 것으로 보입니다.src/components/dailyRecord/SharedDiaryChat.tsx (2)
32-40: LGTM!모달 모드에서 body 스크롤을 비활성화하고 cleanup 함수에서 복원하는 로직이 올바르게 구현되어 있습니다.
82-99: 모달 모드에서 onSend 호출 시 동작을 검토하세요.모달 모드(
!isPage)에서onFocus는 페이지로 네비게이션하지만,onSend는 여전히 로컬 state를 업데이트합니다.MessageInput에서 포커스 이벤트가 먼저 발생하므로 모달에서는onSend가 실제로 호출되지 않을 것으로 예상되나, 의도된 동작인지 확인이 필요합니다.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| <div className="w-full px-4 py-[10px]"> | ||
| <button className="h-[14px] w-[14px] cursor-pointer"> | ||
| <CloseIcon | ||
| onClick={() => router.back()} | ||
| className="h-[14px] w-[14px] text-black" | ||
| /> | ||
| </button> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
닫기 버튼의 클릭 핸들러 위치가 잘못되었습니다.
onClick 핸들러가 <CloseIcon> 컴포넌트에 있지만, 상위 <button> 요소에 있어야 합니다. SVG 컴포넌트가 onClick prop을 지원하지 않거나 이벤트를 전달하지 않으면 클릭이 작동하지 않을 수 있습니다.
🐛 제안된 수정
- <button className="h-[14px] w-[14px] cursor-pointer">
- <CloseIcon
- onClick={() => router.back()}
- className="h-[14px] w-[14px] text-black"
- />
+ <button
+ className="h-[14px] w-[14px] cursor-pointer"
+ onClick={() => router.back()}
+ >
+ <CloseIcon className="h-[14px] w-[14px] text-black" />
</button>🤖 Prompt for AI Agents
In `@src/components/dailyRecord/SharedDiaryChat.tsx` around lines 59 - 65, The
click handler is placed on the CloseIcon SVG but should be on the parent button;
move the onClick={() => router.back()} from the CloseIcon element to the
surrounding <button> so the button element handles the click (keep CloseIcon as
a presentational child and preserve className and sizing on both the button and
CloseIcon).
🔥 작업 내용
🤔 추후 작업 사항
📸 작업 내역 스크린샷
2026-01-07.4.35.46.mov
🔗 이슈
Summary by CodeRabbit
릴리스 노트
새로운 기능
개선
스타일
기타
✏️ Tip: You can customize this high-level summary in your review settings.